作者:UNESCO媒介与女性教席走_890 | 来源:互联网 | 2024-11-17 21:24
本文由编程笔记小编整理,旨在介绍链接脚本和可执行文件的相关知识,帮助读者深入理解这些概念。
基本概念
以下是关于链接器、链接脚本和可执行程序的重要概念:
链接器(Linker)
链接器是一个程序,其主要功能是将多个目标文件(包括标准库函数的目标文件)的代码段、数据段及符号表等内容整合成一个符合ELF或EXE等格式的可执行二进制文件。
链接脚本
链接器在链接过程中需要使用链接脚本。如果没有通过“-T”参数指定链接脚本,链接器会使用默认的内部脚本。链接脚本的主要作用是将输入文件的段按指定的地址空间布局合并到输出文件的段中,使输出文件具备可执行性。
可执行程序
任何可执行程序,无论是Windows的.exe文件还是Linux的.elf文件,都由代码段、数据段、未初始化的数据段等部分组成。
段(Section)
每个输出段有两个地址:虚拟地址(Virtual Memory Address, VMA):运行时的地址;加载地址(Load Memory Address, LMA):加载时的地址。如果没有通过“AT”指定LMA,则LMA等于VMA。在嵌入式系统中,加载地址和运行地址常常不同,例如,程序可能存储在Flash中,但在运行时复制到RAM中。
分析S32K144的工程
我们以S32K1xx_flash_debug.ld为例,解析其内存映射和链接脚本的细节。
1. 内存映射(Memory Map)是处理器的存储布局,描述了CPU指令集可以直接寻址的外部存储空间。这些存储器可以是Flash、NAND Flash、SRAM、DDR等。
/* Specify the memory areas */
MEMORY
/* Flash */
m_interrupts (RX) : ORIGIN = 0x00000000, LENGTH = 0x00000400
m_flash_config (RX) : ORIGIN = 0x00000400, LENGTH = 0x00000010
m_text (RX) : ORIGIN = 0x00000410, LENGTH = 0x0007FBF0
/* SRAM_L */
m_data (RW) : ORIGIN = 0x1FFF8000, LENGTH = 0x00008000
/* SRAM_U */
m_data_2 (RW) : ORIGIN = 0x20000000, LENGTH = 0x00007000
2. 目标可执行文件由输入文件的各个段填充,具体的填充规则和地址定义在链接脚本中。
中断向量和OS的异常向量表存储在m_interrupts段中,该段位于地址空间的ORIGIN = 0x00000000, LENGTH = 0x00000400。
.interrupts :
__VECTOR_TABLE = .;
. = ALIGN(4);
"*(.isr_vector)"
"*(Os_ExceptionVectors)" /* Startup code */
. = ALIGN(4);
> m_interrupts
代码段(.code)的虚拟地址(运行时地址)位于m_text (RX) : ORIGIN = 0x00000410, LENGTH = 0x0007FBF0,数据段的虚拟地址(运行时地址)位于m_data,bss段的虚拟地址(运行时地址)位于m_data_2。注意,段的虚拟地址和存储地址是不同的。
3. 链接脚本中的地址信息会被程序引用,因此链接脚本也是程序的一部分,可以看作是一个头文件。
__etext = .; /* Define a global symbol at end of code. */
__DATA_ROM = .; /* Symbol is used by startup for data initialization. */
.data : AT(__DATA_ROM)
. = ALIGN(4);
__DATA_RAM = .;
__data_start__ = .; /* Create a global symbol at data start. */
"*(.data)" /* .data sections */
"*(.data*)" /* .data* sections */
"*(.os_data)"
"*(.mcal_data)"
"*(.jcr*)"
. = ALIGN(4);
__data_end__ = .; /* Define a global symbol at data end. */
> m_data
虚拟地址是程序运行时的地址,加载地址是程序存储时的地址或加载源地址。
数据段定义了两个宏地址,这些宏会被C代码引用。
__DATA_ROM是紧接在代码段之后的一个地址,AT(__DATA_ROM)表示将.data段的加载地址设置为__DATA_ROM。
在startup_init_bss.c中引用了这个地址:
void init_data_bss(void)
data_rom = (uint8_t *)__DATA_ROM;
/* Copy initialized data from ROM to RAM */
while (data_rom_end != data_rom)
*data_ram = *data_rom;
data_ram++;
data_rom++;
在startup.s中使用init_data_bss,将.data段从加载地址拷贝到运行地址(实际上是将数据从Flash拷贝到SRAM)。即使不拷贝,只要CPU可以寻址,程序也能执行,因为Flash和SRAM都在地址空间内。
; Init .data and .bss sections
LDR R0,=init_data_bss
BLX R0
;cpsie i ; Unmask interrupts
BL main
从规格书中查找memory map:
总结:
1. 可执行程序由数据段、代码段、bss段等组成。
2. 每个段对应两个地址:虚拟地址(运行地址)和加载地址(存储地址),这些地址都在CPU可寻址的地址空间内。
3. 链接脚本中的地址信息会被C代码引用,链接脚本可以视为程序的一部分(类似头文件)。
4. CPU的memory map对应的存储器可以是Flash、SRAM、DDR等。如果加载地址和运行地址不同,程序在启动时(如在startup.s中)需要将加载地址的内容复制到运行地址。加载地址和运行地址由程序员根据存储器特性自定义。